#pragma once

#include <ostream>
#include <string>
#include <vector>
#include <stack>
#include <list>
#include <map>
#include <set>

#include "../endian.hpp"
#include "../util/verify.hpp"
#include "bin_istream.hpp"




namespace boostext
{
	namespace iostreams
	{
		using namespace boost::iostreams;

		/**
		* The class bin_ostream is a wrapper around a std::basic_ostream.
		* But instead of providing formatted output, data is written in a binary
		* manner from the stream.
		*
		* The stream can even be instructed to write POD types with another
		* endian ordering, but will (unless told otherwise) use the current
		* system's endian ordering.
		*/
		class bin_ostream
		{
		public:

			virtual ~bin_ostream() {}



			/**
			* Set the endianess of the stream.
			*/
			virtual void setEndian(endian_ordering ordering) = 0;

			/**
			* Get the endianess of the stream.
			*/
			virtual endian_ordering endian() const = 0;



			/**
			* Get the current position of the stream.
			*/
			virtual std::streamsize pos() = 0;

			/**
			* Get the total size of the stream.
			*/
			virtual std::streamsize size() = 0;



			/**
			* Advances the read/write head to pos, where pos
			* is the relative offset to the start of the stream.
			*/
			virtual void seek(std::streamsize pos) = 0;

			/**
			* Advances the read/write head by off characters,
			* where the offset is calculated from:
			*  - the start of the sequence if way == ios_base::beg
			*  - the current position if way == ios_base::cur
			*  - the end of the sequence if way == ios_base::end
			*/
			virtual void seek(stream_offset off, std::ios_base::seekdir way) = 0;



			/**
			* Delegates writing a number of characters to the actual ostream.
			*/
			virtual void write(const void* data, std::streamsize n) = 0;



			/**
			 * Write an entire istream to this stream
			 */
			bin_ostream& operator<<(bin_istream& that)
			{
				std::streamsize n = that.left();
				write(that.directSequence(n), n);

				return *this;
			}

			/**
			* Write a string to this stream.
			*/
			bin_ostream& operator<<(const std::string& str)
			{
				// For 32bit compatibility, every string's size is serialized as a uint32
				uint32 size = static_cast<uint32>(str.size());

				(*this) << size;
				write(str.c_str(), str.size() * sizeof(char));

				return *this;
			}

			/**
			* Write a wstring to this stream.
			*/
			bin_ostream& operator<<(const std::wstring& str)
			{
				// For 32bit compatibility, every string's size is serialized as a uint32
				uint32 size = static_cast<uint32>(str.size());

				(*this) << size;
				write(str.c_str(), str.size() * sizeof(wchar_t));

				return *this;
			}

			/**
			* Write a float to this stream.
			*/
			bin_ostream& operator<<(float value)
			{
				write_mind_endian(value);
				return *this;
			}

			/**
			* Write a double to this stream.
			*/
			bin_ostream& operator<<(double value)
			{
				write_mind_endian(value);
				return *this;
			}

			/**
			* Write an uint64 to this stream.
			*/
			bin_ostream& operator<<(uint64 value)
			{
				write_mind_endian(value);
				return *this;
			}

			/**
			* Write an int64 to this stream.
			*/
			bin_ostream& operator<<(int64 value)
			{
				write_mind_endian(value);
				return *this;
			}

			/**
			* Write an uint32 to this stream.
			*/
			bin_ostream& operator<<(uint32 value)
			{
				write_mind_endian(value);
				return *this;
			}

			/**
			* Write an int32 to this stream.
			*/
			bin_ostream& operator<<(int32 value)
			{
				write_mind_endian(value);
				return *this;
			}

			/**
			* Write an uint16 to this stream.
			*/
			bin_ostream& operator<<(uint16 value)
			{
				write_mind_endian(value);
				return *this;
			}

			/**
			* Write an int16 to this stream.
			*/
			bin_ostream& operator<<(int16 value)
			{
				write_mind_endian(value);
				return *this;
			}

			/**
			* Write an uint8 to this stream.
			*/
			bin_ostream& operator<<(uint8 value)
			{
				write(&value, sizeof(value));
				return *this;
			}

			/**
			* Write an int8 to this stream.
			*/
			bin_ostream& operator<<(int8 value)
			{
				write(&value, sizeof(value));
				return *this;
			}

			/**
			* Write a bool to this stream.
			*/
			bin_ostream& operator<<(bool value)
			{
				write(&value, sizeof(value));
				return *this;
			}



			/**
			* Write a std::pair to the ostream. The first
			* member is serialized in front of the second.
			* This method requires a streaming operator
			* for both the key and the value type.
			*/
			template <typename Key, typename Value>
			bin_ostream& operator<<(const std::pair<Key,Value>& pair)
			{
				(*this) << pair.first;
				(*this) << pair.second;

				return *this;
			}

			/**
			* Write a boost::optional to the ostream.
			* The method will write the flag, indicating
			* if the actual value follows or not, as well
			* as the actual value (when optional carries a value).
			*/
			template <typename T>
			bin_ostream& operator<<(const boost::optional<T>& opt)
			{
				(*this) << opt.is_initialized();

				if( opt.is_initialized() )
					(*this) << opt.get();

				return *this;
			}

			/**
			* Write a std::vector to the ostream.
			* The size of the vector is serialized as well as
			* each element on its own. This method
			* requires a streaming operator for the
			* value type.
			*/
			template <typename T, class Ax>
			bin_ostream& operator<<(const std::vector<T,Ax>& vector)
			{
				// Write the size (number of elements)
				// For 32bit compatibility, every container's size is serialized as a uint32
				uint32 size = static_cast<uint32>(vector.size());
				(*this) << size;

				// The following code assumes that there are elements to write and would
				// crash when the size is 0. However since there is nothing to do in this
				// case, we can quit right here.
				if(!size)
					return *this;

				// When the underlying type employs a trivial assignment operator
				// (or actually is a POD-type), then a memcpy over the entire sequence (size * sizeof(T))
				// is used, to write data into the ostream. Otherwise it is written element
				// by element.
				if(BOOST_HAS_TRIVIAL_ASSIGN(T))
				{
					write(&vector.front(), vector.size());
				}
				else
				{
					write_sequence(vector.begin(), vector.end());
				}

				return *this;
			}

			/**
			* Write a std::list to the ostream.
			* The size of the list is serialized as well as
			* each element on its own.
			* This method requires a streaming operator for
			* the value type.
			*/
			template <typename T, class Ax>
			bin_ostream& operator<<(const std::list<T,Ax>& list)
			{
				// Write the size (number of elements)
				// For 32bit compatibility, every container's size is serialized as a uint32
				uint32 size = static_cast<uint32>(list.size());
				(*this) << size;

				// And then write each element on its own to the ostream
				write_sequence(list.begin(), list.end());

				return *this;
			}

			/**
			* Write a std::map to the ostream.
			* The size of the map is serialized as well as
			* each key => value pair.
			* This method requires a streaming operator for
			* the key and value type.
			*/
			template <typename Key, typename Value>
			bin_ostream& operator<<(const std::map<Key,Value>& map)
			{
				// Write the size (number of elements)
				// For 32bit compatibility, every container's size is serialized as a uint32
				uint32 size = static_cast<uint32>(map.size());
				(*this) << size;

				// And then write each element on its own to the ostream
				write_sequence(map.begin(), map.end());

				return *this;
			}

			/**
			* Write a std::set to the ostream.
			* The size of the set is serialized as well as
			* each element.
			* This method requires a streaming operator for
			* the value type.
			*/
			template <typename T, class Ax>
			bin_ostream& operator<<(const std::set<T,Ax>& set)
			{
				// Write the size (number of elements)
				// For 32bit compatibility, every container's size is serialized as a uint32
				uint32 size = static_cast<uint32>(set.size());
				(*this) << size;

				// And then write each element on its own to the ostream
				write_sequence(set.begin(), set.end());

				return *this;
			}

		private:

			/**
			* Write a sequence of elements to the ostream.
			* Mostly used for serializing stl-containers.
			*/
			template <typename Iter>
			void write_sequence(Iter _First, Iter _Last)
			{
				while(_First != _Last)
				{
					(*this) << *_First;
					++_First;
				}
			}




			/**
			* Write a value of an arbitrary type to the ostream, while
			* paying attention to endian-byte-ordering that is set by the user.
			* By default, the system's endianness is used.
			*/
			template <typename T>
			void write_mind_endian(T value)
			{
				endian_ordering ordering = endian();
				if(ordering == little_endian_ordering)
				{
					little_endian<T> tmp(value);
					write(&tmp, sizeof(tmp));
				}
				else if(ordering == big_endian_ordering)
				{
					big_endian<T> tmp(value);
					write(&tmp, sizeof(tmp));
				}
				else
				{
					BOOSTEXT_THROW(invalid_value_exception(ordering));
				}
			}
		};
		///////////////////////////////////////////////////////////////////////////////////////////




		template <typename Device,bool direct /*= true*/>
		class bin_ostream_base : public bin_ostream
		{
		public:

			typedef typename category_of<Device>::type    mode;
			typedef typename char_type_of<Device>::type   char_type;

		public:

			void write(const void* data, std::streamsize n)
			{
				if(n == 0)
					return;

				Device::pair_type p = (*this)->output_sequence();
				std::streamsize w = min(left(), n);
				seek(w, std::ios_base::cur);

				if(w != n)
					BOOSTEXT_THROW(ios_failure("Couldn't write requested size"));

				::memcpy(p.first, data, n);
			}

			std::streamsize size()
			{
				Device::pair_type p = (*this)->output_sequence();
				return p.second - p.first;
			}



			virtual Device& operator*() = 0;
			virtual const Device& operator*() const = 0;
			virtual Device* operator->() = 0;
			virtual const Device* operator->() const = 0;
		};
		///////////////////////////////////////////////////////////////////////////////////////////




		template <typename Device>
		class bin_ostream_base<Device,/*bool direct =*/ false> : public bin_ostream
		{
		public:

			typedef typename category_of<Device>::type    mode;
			typedef typename char_type_of<Device>::type   char_type;

		public:

			void write(const void* data, std::streamsize n)
			{
				std::streamsize w = (*this)->write(reinterpret_cast<const char*>(data), n);
				seek(w, std::ios_base::cur);

				if(w != n)
					BOOSTEXT_THROW(ios_failure("Couldn't write requested size"));
			}

			std::streamsize size()
			{
				return (*this)->size();
			}



			virtual Device& operator*() = 0;
			virtual const Device& operator*() const = 0;
			virtual Device* operator->() = 0;
			virtual const Device* operator->() const = 0;
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
